package com.log320.huffman;

import java.io.BufferedOutputStream;
import java.io.IOException;

/**
 * Wrapper around a BufferedOutputStream to handle writing of bits.
 * Used composition instead of inheritance to avoid coupling with the java API
 * 
 * The majority of the methods in this class come directly or have been slightly
 * adapted from source code from Princeton University
 * http://algs4.cs.princeton.edu/55compression/BinaryStdOut.java.html
 * 
 * @author Marc-Antoine Bilodeau
 *
 */
public class BitOutputStream {
	private static BufferedOutputStream out;
	private static int buffer;     // 8-bit buffer of bits to write out
    private static int N;          // number of bits remaining in buffer
    
    
    public BitOutputStream(BufferedOutputStream bos) { 
    	if (bos==null){
    		out = new BufferedOutputStream(System.out);
    	}else{
    		out = bos;
    	}
    }
    /**
     * Write the specified bit to standard output.
     * @param x the <tt>boolean</tt> to write.
     */
    public void write(boolean bitValue) {
        writeBit(bitValue);
    } 
    
    /**
     * Write the 8-bit byte to standard output.
     * @param x the <tt>byte</tt> to write.
     */
    public static void write(byte x) {
        writeByte(x & 0xff);
    }

   /**
     * Write the 32-bit int to standard output.
     * @param x the <tt>int</tt> to write.
     */
    public static void write(int x) {
        writeByte((x >>> 24) & 0xff);
        writeByte((x >>> 16) & 0xff);
        writeByte((x >>>  8) & 0xff);
        writeByte((x >>>  0) & 0xff);
    }

    
    /**
     * Write the specified bit to standard output.
     */
    private static void writeBit(boolean bit) {
        // add bit to buffer
        buffer <<= 1;
        if (bit) buffer |= 1;

        // if buffer is full (8 bits), write out as a single byte
        N++;
        if (N == 8) clearBuffer();
    }
    
    /**
     * Write the 8-bit byte to standard output.
     */
    public static void writeByte(int x) {
        assert x >= 0 && x < 256;

        // optimized if byte-aligned
        if (N == 0) {
            try { out.write(x); }
            catch (IOException e) { e.printStackTrace(); }
            return;
        }

        // otherwise write one bit at a time
        for (int i = 0; i < 8; i++) {
            boolean bit = ((x >>> (8 - i - 1)) & 1) == 1;
            writeBit(bit);
        }
    }
    
    // write out any remaining bits in buffer to standard output, padding with 0s
    private static void clearBuffer() {
        if (N == 0) return;
        if (N > 0) buffer <<= (8 - N);
        try { out.write(buffer); }
        catch (IOException e) { e.printStackTrace(); }
        N = 0;
        buffer = 0;
    }
    
    /**
     * Flush standard output, padding 0s if number of bits written so far
     * is not a multiple of 8.
     */
    public static void flush() {
        clearBuffer();
        try { out.flush(); }
        catch (IOException e) { e.printStackTrace(); }
    }
    
    public void close() {
        flush();
        try { out.close(); }
        catch (IOException e) { e.printStackTrace(); }
    }
}
